<?php 
/**
 * This file is part of DomAr.
 * 
 * DomAr is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * DomAr is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with DomAr.  If not, see <http://www.gnu.org/licenses/>.
 * 
 * @link http://inditel.ee/
 * @copyright 2007-2009 Inditel Meedia OÜ
 * @author Oliver Leisalu <oliver@inditel.ee>
 */

/**
* @package DomAr
* @subpackage Relation
*/
class DomArOneToOne extends DomArProperty_Plugin implements 
																				IDomArPropertyAlterTableListener,
																				IDomArPropertyBeforeInsertListener,
																				IDomArPropertyInsertListener,
																				IDomArPropertyBeforeUpdateListener,
																				IDomArPropertyUpdateListener,
																				IDomArPropertyBeforeDeleteListener, 
																				IDomArPropertyBeforeSetListener,
																				IDomArPropertyAfterSetListener,
																				IDomArPropertySerializeListener {
	
	/**
	* @param String Child class name
	*/
	protected $childClassName;
	
	/**
	* @param String Parent class nams
	*/
	protected $className;
	
	/**
	* @param String Inversed property name
	*/
	protected $inverse = NULL;
	
	/**
	* @param Boolean
	*/
	protected $actAsParent = true;
	
	/**
	* @param Boolean
	*/
	protected $composition = false;
	
	
	/**
	* @param String $className
	* @param String $name
	* @param String $childClassName
	* @param Boolean $composition
	* @param Boolean $actAsParent
	* @param String $inverse
	* @param Boolean $inverseToMany 
	*/
	public function __construct( $className, $name, $childClassName, $composition = false, $actAsParent = true, $inverse = NULL ) {
		parent::__construct( $name );
		$this->childClassName = $childClassName;
		$this->composition = (bool)$composition;
		$this->className = $className;
		$this->actAsParent = (bool)$actAsParent;
		$this->inverse = $inverse;
	}

	/**
	* @param DomArObject $object
	* @param Int|DomArObject $newPropertyValue
	*/
	public function beforeSet( DomArObject $object, &$oldPropertyValue, &$newPropertyValue ) {
		
		// Dealing with old property value
		if( is_object($oldPropertyValue)) {
			
			// it is composition so child should be deleted
			if( $this->composition === true && $oldPropertyValue->id > 0) {
				$oldPropertyValue->delete();
			
			// it is not composition so we just need to remove relation
			} else {
				
				// invoking child listeners and setting value to NULL
				$null = NULL;
				$oldPropertyValue->_beforeSet( $this->inverse, $null );
				$oldPropertyValue->forceSet( $this->inverse, $null);
				$oldPropertyValue->setChanged( $this->inverse, true );
				$oldPropertyValue->_afterSet( $this->inverse, $null );
				
				// removing relation from database.
				if( $object->id > 0 && $oldPropertyValue->id > 0) {
					if( $this->actAsParent === true ) {
						DomSql::query('UPDATE '.$oldPropertyValue->getTableName().' SET '.$this->inverse.' = NULL WHERE id='.$oldPropertyValue->getId().' LIMIT 1');
					} else {
						DomSql::query('UPDATE '.$object->getTableName().' SET '.$this->name.' = NULL WHERE id='.$object->getId().' LIMIT 1');
					}
				}
			}

		}
		
		// Dealing with new property value. Continues in afterSet listener.
		if( is_object($newPropertyValue) && $this->inverse ) {
			$newPropertyValue->_beforeSet( $this->inverse, $object );
		}
		
	}
	
	/**
	* @param DomArObject $object
	* @param Int|DomArObject $propertyValue
	*/	
	public function afterSet( DomArObject $object, &$propertyValue ) {
		
		if( is_object($propertyValue) && $this->inverse ) {
			$propertyValue->forceSet( $this->inverse,  $object );
			$propertyValue->setChanged( $this->inverse, true );
			$propertyValue->_afterSet( $this->inverse, $object );
		}
	}
	
	/**
	* @param DomArObject $object
	* @param Array $values
	* @return Int
	*/
	public function init( DomArObject $object, array &$values ) {
		return (int)$values[$this->name];
	}
	
	/**
	* @param DomArObject $object
	* @param Int $propertyValue
	* @return DomArObject
	*/
	public function load( DomArObject $object, &$propertyValue ) {
		if( $this->actAsParent === true ) {
			if( $object->getId() == 0 )
				return NULL;
				
			$data = DomSql::selectOneQuery('SELECT * FROM '.call_user_func(array($this->childClassName,'getTableName')).' as t WHERE '.$this->inverse.' = '.$object->getId().' LIMIT 1');
			if( $data )
				return call_user_func_array(array($data['className'],'create'), array(&$data) );
			else
				return NULL;
		} else {
			if( $propertyValue == 0 )
				return NULL;
			return DomAr::load( $propertyValue, $this->childClassName);	
		}
	}
	
	/**
	* @param DomArObject $object
	* @param Int|DomArObject $propertyValue
	* @return Boolean
	*/
	public function isLoaded( DomArObject $object, &$propertyValue ) {
		return is_object($propertyValue) or $propertyValue === NULL;
	}
	
	/**
	* Validetes if object can be added as child.
	* Allows 0 or object that extends to $childClassName and their tablenames match.
	* @param DomArObject $object
	* @param Int|DomArObject $value
	* @param Array $customParams
	* @return Int
	*/
	public function validate( DomArObject $object, &$value, array &$customParams = array() ) {	
		if( $value != 0 ) {
			// checking that child is from correct class and has same tableName
			$childTableName = call_user_func(array(get_class($value), 'getDbTable'));
			$parentTableName = call_user_func(array($this->childClassName, 'getDbTable'));
			
			if( !($value instanceof $this->childClassName) or $childTableName != $parentTableName ) {
				return 1;
			}
				
			// Checking that child custom listeners allow this parent.
			if( $this->inverse ) {
				$childValidate = $value->_validate( $this->inverse, $object, true );
				if( $childValidate !== 0)
					return $childValidate;
			}
		}
		
		// Checking that current child listeners allow NULL value
		if( $this->inverse && is_object($object->{$this->name}) ) {
			$null = NULL;
			$childValidate = $object->{$this->name}->_validate( $this->inverse, $null, true );
			if( $childValidate !== 0)
				return $childValidate;
		}
			
		return 0;
	}
	
	public function beforeInsert( DomArSession $session, DomArObject $object, &$propertyValue ) {
		if( !$propertyValue ) return;
		$session->add( $propertyValue );
		//if( $this->actAsParent === false)
			//$session->addRelation( $object, $propertyValue );
	}
	
	public function insert( DomArObject $object, &$propertyValue, DomArValueSet $set ) {        
		if( $this->actAsParent === false ) {
			if( $propertyValue->id > 0 ) {
				$set->set( $this->name, (int)$propertyValue->id );
			} else {
				$object->setChanged( $this->name, true );
				$set->set( $this->name, 'NULL' );
			}
		}
	}

	public function beforeUpdate( DomArSession $session, DomArObject $object, &$propertyValue ) {

		if( !$propertyValue ) return;
		$session->add( $propertyValue );
		//if( $this->actAsParent === false )
			//$session->addRelation( $object, $propertyValue );
	}
	
	public function update( DomArObject $object, &$propertyValue, DomArValueSet $set ) {
		if( $this->actAsParent === false )
			$set->set( $this->name, ($propertyValue->id > 0 ? (int)$propertyValue->id : 'NULL') );
	}

	/**
	* @param DomArTable $table Parent table.
	*/
	public function alterTable( DomArTable $table ) {
		if( $this->actAsParent === false ) {
			$table->changeColumn( $this->name, 'bigint', true );
			$table->changeIndex( $table->tableName.'_'.$this->name, array($this->name), 'unique' );
		}
		$childTable = call_user_func( array($this->childClassName, 'getTable') )->update();
	}
	
	/**
	* Removes parent from child if inversed.
	* @param DomArObject $object
	* @param DomArObject $propertyValue
	*/
	public function beforeDelete( DomArObject $object, &$propertyValueRef ) {
		
		$propertyValue = $propertyValueRef;
		
		$object->forceSet( $this->name, NULL);
	
		if( is_object($propertyValue) === true  ) {
		
			$propertyValue->forceSet( $this->inverse, NULL);
			
			if( $propertyValue->id > 0 ) {
				if( $this->composition === true )
					$propertyValue->delete();	
				
				else if( $this->actAsParent === true && $object->id > 0 )
					DomSql::query('UPDATE '.$propertyValue->getTableName().' SET '.$this->inverse.' = NULL WHERE id = '.$propertyValue->id.' LIMIT 1');
				
				if( $this->composition === false && $this->inverse !== NULL && $object->id > 0 ) {
					$propertyValue->save();
				}
			}
		}
	}
	
	/**
	* Prepares parent for serializing. 
	* If child is saved returns child id, if not prepares child for serialization and returns it.
	* If child is not loaded then returns id.
	* @param DomArObject $object
	* @param DomArObject $propertyValue
	* @return Int|DomArObject
	*/
	public function serialize( DomArObject $object, $propertyValue ) {
		if( is_object($propertyValue)  ) {
			if( $propertyValue->getId() && !$propertyValue->isChanged() ) {
				return $propertyValue->getId();
			} else {
				$propertyValue->_serializeData();
			}
		}
		return $propertyValue;
	}
	
	/**
	* Unserializes child if its loaded. If its loaded means its not yet saved or it was changed.
	* @param DomArObject $object
	* @param DomArObject $propertyValue
	*/
	public function unserialize( DomArObject $object, $propertyValue ) {
		//if( !is_numeric( $propertyValue ) && !is_object($propertyValue) )
		//	$propertyValue = DomArObject::unserialize($propertyValue);
		if( is_object($propertyValue) )
			$propertyValue->_unserializeData();
		
		return $propertyValue;
	}
	
}
?>